/**
 * Flickr Photo
 * @param {Flickr.PhotoSummary} api JSON response from Flickr API call
 * @param {Array.<Enum.size>} imageSizes Image size URLs to retrieve from Flickr
 * @param {Photo} [previous] Photo before this one
 * @constructor
 */
function Photo(api, imageSizes, previous)
{
	"use strict";

	/** @type {Photo} */
	var _this = this;
	/** @type {Location} */
	var _location = null;
	/** @type {google.maps.InfoWindow} */
	var _infoWindow = null;
	/** @type {String} Flickr ID */
	this.id = api.id;
	/** @type {Size} */
	this.size = null;
	/** @type {Exif} */
	this.exif = null;
	/**
	 * Whether image has been resized to fit the screen
	 * @type {Boolean}
	 */
	this.resized = false;
	/**
	 * HTML Image tag
	 * @type {Image}
	 */
	this.image = null;
	/** @type {Photo} */
	this.previous = previous;
	/** @type {Photo} */
	this.next = null;
	/** @type {Size} */
	this.mapSize = null;
	/** @type {Size} */
	this.stripSize = null;
	/** @type {String} */
	this.title = Format.text(api.title);
	/** @type {String} */
	this.description = Format.story(api.description._content);
	/** @type {Date} */
	this.dateTaken = Format.parseDate(api.datetaken);
	/** @type {google.maps.Marker} */
	this.mapMarker = null;
	/** @type {Boolean} */
	this.previewOnMap = false;
	/**
	 * Whether image bytes have been downloaded and assigned to this.image
	 * @type {Boolean}
	 */
	this.imageReady = false;

	function init()
	{
		parseLocation(api);
		parseSizes(api, imageSizes);

		api = null;

		if (previous != null) { previous.next = _this; }
	}

	/**
	 * Call a method when the photo's onload event has fired
	 * @param {Function} callback Method to call when image.onload has fired
	 */
	this.whenReady = function(callback)
	{
		if (_this.imageReady) { callback(); }
		else { _this.image.onload = function() { _this.imageReady = true; callback(); } }
	};

	/**
	 * Create image object and set src to begin download from server
	 * @return {Boolean} Whether the image was ready and could be loaded
	 */
	this.download = function()
	{
		if (!_this.imageReady)
		{
			_this.image = new Image();
			_this.image.id = 'photo-' + _this.id;
			_this.image.src = _this.size.url;			// setting as source triggers download
			_this.image.height = _this.size.height;
			_this.image.width = _this.size.width;
			_this.image.onload = function() { _this.imageReady = true; };
			return true;
		}
		return false;
	};

	/**
	 * Add photo to given map
	 * @param {google.maps.Map} map
	 * @param {String|google.maps.MarkerImage} mark
	 * @param {String|google.maps.MarkerImage} shadow
	 * @param {Function(PhotoObject)} clickHandler Method to call when a photo marker is clicked
	 */
	this.addToMap = function(map, mark, shadow, clickHandler)
	{
		if (_this.mapMarker == null && _location != null)
		{
			_this.mapMarker = new google.maps.Marker(
				{
					position: new google.maps.LatLng(_location.latitude, _location.longitude),
					title: _this.title
				}
			);
			if (mark != null)
			{
				_this.mapMarker.setIcon(mark);
				_this.mapMarker.setShadow(shadow);

				if (_infoWindow == null)
				{
					_infoWindow = new google.maps.InfoWindow({
						content: '<div class="info-photo"><img src="' + _this.mapSize.url + '"/></div>'
					});
				}

				google.maps.event.addListener(_this.mapMarker, 'mouseover', function()
				{
					if (_this.previewOnMap) { _infoWindow.open(map, _this.mapMarker); }
				});

				google.maps.event.addListener(_this.mapMarker, 'mouseout', function()
				{
					if (_this.previewOnMap) { _infoWindow.close(); }
				});

				if (clickHandler != null)
				{
					google.maps.event.addListener(_this.mapMarker, 'click', function()
					{
						if (_this.previewOnMap) { clickHandler(_this); }
					});
				}
			}
		}
		else if (_location != null)
		{
			_this.mapMarker.setMap(map);
		}
	};

	/**
	 * Remove photo from map by setting map marker to null
	 */
	this.removeFromMap = function()
	{
		if (_this.mapMarker != null) { _this.mapMarker.setMap(null); }
	};

	this.closeInfoWindow = function() { if (_infoWindow != null) { _infoWindow.close(); } };

	/**
	 * Whether photo is larger than given dimensions
	 * @param {Number} width
	 * @param {Number} height
	 * @return {Boolean}
	 */
	this.largerThan = function(width, height)
	{
		return (_this.size.width > width || _this.size.height > height);
	};

	/**
	 * Restore image to its original dimensions
	 */
	this.resetSize = function()
	{
		_this.image.width = _this.size.width;
		_this.image.height = _this.size.height;
		_this.resized = false;
	};

	/**
	 * Calculate image width and height to fit constraints
	 * @param {Number} maxWidth
	 * @param {Number} maxHeight
	 */
	this.fitToSize = function(maxWidth, maxHeight)
	{
		if (_this.resized) { return; }	 // already done

		var s = _this.size;
		var maxRatio = maxWidth / maxHeight;
		var imgRatio = s.width / s.height;
		var h, w;

		if (Math.abs(maxRatio - imgRatio) > Enum.maxRatioDiff)
		{
			if (imgRatio < maxRatio)
			{ maxHeight = maxWidth / (imgRatio + Enum.maxRatioDiff); }
			else { maxWidth = maxHeight * (imgRatio - Enum.maxRatioDiff); }
		}

		if (imgRatio > maxRatio)
		{
			w = maxWidth;
			h = s.height * (maxWidth / s.width);
		}
		else
		{
			h = maxHeight;
			w = s.width * (maxHeight / s.height);
		}
		_this.resized = true;
		_this.download();		// does nothing if already downloaded
		_this.image.width = w;
		_this.image.height = h;
	};

//- photo parsing -------------------------------------------------------------

	/**
	 * Extract longitude and latitude from flickr API
	 * @param {Flickr.PhotoSummary} api Portion of JSON response from Flickr API call
	 */
	function parseLocation(api)
	{
		if (api.latitude && api.longitude)
		{
			_location = new Location(api.latitude, api.longitude);
		}
	}

	/**
	 * Create SizeObject members for given sizes from the Flickr API response
	 * @param {Flickr.PhotoSummary} api Portion of JSON response from Flickr API call
	 * @param {Array.<Enum.size>} sizes
	 */
	function parseSizes(api, sizes)
	{
		/** @type {Size} */
		var size;
		/** @type {Enum.size} */
		var id;
		var maxWidth = screen.width + (screen.width * 0.2);
		var maxHeight = screen.height + (screen.height * 0.3);
		/** Width below which any height is allowed */
		var maxWidthForAnyHeight = screen.width * 0.8;

		for (var i = 0; i < sizes.length; i++)
		{
			id = sizes[i];
			size = new Size(api, id);
			_this.size = bestSize(maxWidth, maxHeight, size, _this.size, maxWidthForAnyHeight);

			if (id == Enum.map.thumbSize) {	_this.mapSize = size; }
			if (id == Enum.filmstrip.thumbSize) { _this.stripSize = size; }
		}
	}

	/**
	 * Compare new size to old to see if it's a better fit
	 * @param {Number} maxWidth
	 * @param {Number} maxHeight
	 * @param {Size} newSize
	 * @param {Size} oldSize
	 * @param {Number} maxWidthForAnyHeight Width below which any height is allowed
	 * @return {Size}
	 */
	function bestSize(maxWidth, maxHeight, newSize, oldSize, maxWidthForAnyHeight)
	{
		var fits = newSize.fits(maxWidth, maxHeight, maxWidthForAnyHeight);
		return (fits && (oldSize == null || newSize.biggerThan(oldSize))) ? newSize : oldSize;
	}

	init();
}
